﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Controls.Base;
using Silvermoon.Shapes;
using System.Drawing;
using Silvermoon.Images;
using Silvermoon.OpenGL;

using Silvermoon.Windows;
using Silvermoon.Utils;
using Silvermoon.Animations;
using Silvermoon.UI;

namespace Silvermoon.Controls
{
    public class ToggleSwitch : ToggleButtonBase
    {
        #region const

        const int PercentShift = 10;
        public const int MaxOffset = 1 << PercentShift;

        public const int DefaultButtonWidth = 48;
        public const string DefaultOnText = "ON";
        public const string DefaultOffText = "OFF";

        #endregion
        #region accessors

        public static readonly PropertyAccessor<ToggleSwitch, int> OffsetProperty = PropertyAccessor.Register<ToggleSwitch, int>("StateOffset",
            e => e.Offset,
            (e, v) => e.Offset = v);

        public static readonly PropertyAccessor<ToggleSwitch, Color> ButtonColorProperty = PropertyAccessor.Register<ToggleSwitch, Color>("ButtonColor",
            e => e.ButtonColor,
            (e, v) => e.ButtonColor = v);

        public static readonly PropertyAccessor<ToggleSwitch, int> ButtonOpacityProperty = PropertyAccessor.Register<ToggleSwitch, int>("ButtonOpacity",
                e => e.ButtonOpacity,
                (e, v) => e.ButtonOpacity = v);


        #endregion
        #region fields

        private Margin buttonMargin = new Margin(0);
        private Margin contentMargin = new Margin(6);
        private Shape button;
        private Shape onShape;
        private Shape offShape;
        private Shape onBackgroundShape;
        private Shape contentChrome;

        private int offset = 0;

        #endregion
        #region props

        /// <summary>
        /// Gets or sets the shape for the background in the on section.
        /// </summary>
        public Shape OnBackgroundShape
        {
            get { return onBackgroundShape; }
            set
            {
                if (onBackgroundShape != value)
                {
                    if (onBackgroundShape != null)
                    {
                        Shapes.Remove(onBackgroundShape);
                        onBackgroundShape.Dispose();
                    }
                    onBackgroundShape = value;
                    Invalidate(InvalidationFlag.Layout);
                }
            }
        }


        /// <summary>
        /// Gets or sets the margin for the ContentChrome shape.
        /// </summary>
        public Margin ContentMargin
        {
            get { return contentMargin; }
            set
            {
                contentMargin = value;
                Invalidate(InvalidationFlag.Layout);
            }
        }

        /// <summary>
        /// Gets or sets the margin for the button.
        /// </summary>
        public Margin ButtonMargin
        {
            get { return buttonMargin; }
            set
            {
                buttonMargin = value;
                Invalidate(InvalidationFlag.Layout);
            }
        }

        /// <summary>
        /// Shape that appears after On and Off shape but before Button shape.
        /// Therefore this would be a partially transparent background, e.g. to clip away parts from On and/or Off shape.
        /// </summary>
        public Shape ContentChrome
        {
            get { return contentChrome; }
            set
            {
                if (contentChrome != value)
                {
                    Shape oldValue = contentChrome;
                    contentChrome = value;
                    OnContentChromeChanged(oldValue);
                }
            }
        }

        protected virtual void OnContentChromeChanged(Shape oldValue)
        {
            if (oldValue != null)
            {
                Shapes.Remove(oldValue);
                oldValue.Dispose();
            }
            Invalidate(InvalidationFlag.Layout);
        }

        /// <summary>
        /// Gets or sets the offset between 0 and 2^ShiftState where 0 is On mode, and 2^ShiftState is Off mode.
        /// </summary>
        protected int Offset
        {
            get { return offset; }
            set
            {
                value = ValidateOffset(value);
                if (offset != value)
                {
                    offset = value;
                    Invalidate(InvalidationFlag.Layout);
                }
            }
        }

        private static int ValidateOffset(int value)
        {
            if (value < 0) value = 0;
            else if (value > 1 << PercentShift) value = 1 << PercentShift;
            return value;
        }

        protected int ButtonWidth
        {
            get { return button.Width; }
        }

        /// <summary>
        /// Gets or sets shape in "On" mode.
        /// </summary>
        public Shape On
        {
            get { return onShape ?? CreateAndInitOn(); }
            set
            {
                if (onShape != value)
                {
                    if (onShape != null)
                    {
                        Shapes.Remove(onShape);
                        onShape.Dispose();
                    }
                    onShape = value;
                    if (onShape != null)
                    {
                        Shapes.Append(Background, onShape);
                    }
                    Invalidate(InvalidationFlag.Appearance | InvalidationFlag.Text);
                }
            }
        }

        /// <summary>
        /// Gets or sets the shape in "Off" mode.
        /// </summary>
        public Shape Off
        {
            get { return offShape ?? CreateAndInitOff(); }
            set
            {
                if (offShape != value)
                {
                    if (offShape != null)
                    {
                        Shapes.Remove(offShape);
                        offShape.Dispose();
                    }
                    offShape = value;
                    if (offShape != null)
                    {
                        Shapes.Append(Background, offShape);
                    }
                    Invalidate(InvalidationFlag.Appearance | InvalidationFlag.Text);
                }
            }
        }

        /// <summary>
        /// Gets or sets the shape for the button.
        /// </summary>
        public Shape ButtonShape
        {
            get { return button; }
            set
            {
                if (button != value)
                {
                    if (button != null)
                    {
                        Shapes.Remove(button);
                        button.Dispose();
                    }
                    button = value;
                    if (button != null) Shapes.Append(Background, button);
                    OnBackgroundChanged();
                    Invalidate(InvalidationFlag.Color);
                }
            }
        }

        /// <summary>
        /// Gets or sets the color for the button.
        /// </summary>
        public Color ButtonColor
        {
            get { return button != null ? button.Color : Color.Empty; ; }
            set
            {
                if (button != null)
                {
                    if (button.Color != value)
                    {
                        button.Color = value;
                        Invalidate(InvalidationFlag.Appearance | InvalidationFlag.Color);
                    }
                }
            }
        }

        public int ButtonOpacity
        {
            get { return button != null ? button.Opacity : 255; }
            set
            {
                if (button != null)
                {
                    if (button.Opacity != value)
                    {
                        button.Opacity = value;
                        Invalidate(InvalidationFlag.Appearance);
                        if (value == 0 || value == 255) Invalidate(InvalidationFlag.Layout);
                    }
                }
            }
        }

        protected int ButtonLeft
        {
            get
            {
                return (Width - Control.UnscaleX(button.Width)) * offset >> PercentShift;
            }
        }

        protected override int DefaultScrollDuration
        {
            get
            {
                return 150;
            }
        }

        #endregion
        #region ctors

        static ToggleSwitch()
        {

            VisualStateManager.RegisterClassManager(typeof(ToggleSwitch), ToggleSwitchManagers.ToggleSwitchManager);
        }


        public ToggleSwitch()
            : base()
        {
            Size = new Size(110, 64);
            button = CreateDefaultButton();
            Shapes.AddRange(button);
            ControlFlags |= ControlFlags.CanPan;
            TextColor = Color.Black;
        }

        protected override void Dispose(bool dispose)
        {
            DisposeObject(offShape);
            DisposeObject(onShape);
            DisposeObject(onBackgroundShape);
            DisposeObject(contentChrome);
            DisposeObject(button);
            base.Dispose(dispose);
        }

        #endregion
        #region methods


        protected internal override void OnEndPan(EventArgs e)
        {
            if (!OffsetProperty.IsAnimated(this)) EnsureRestState(true);
            base.OnEndPan(e);
        }

        private Shape CreateAndInitOn()
        {
            onShape = CreateOnShape();
            Shapes.Append(Background, onShape);
            return onShape;
        }


        private Shape CreateAndInitOff()
        {
            offShape = CreateOffShape();
            Shapes.Append(Background, offShape);
            return offShape;
        }

        /// <summary>
        /// Creates the default shape in on mode.
        /// </summary>
        /// <returns>Shape.</returns>
        protected virtual Shape CreateOnShape()
        {
            return new StringShape { Alignment = Alignment.Center, LineAlignment = Alignment.Center, Text = DefaultOnText, LineMode = LineMode.SingleLine, Color = Color.Black };
        }

        /// <summary>
        /// Creates the default shape in off mode.
        /// </summary>
        /// <returns>Shape.</returns>
        protected virtual Shape CreateOffShape()
        {
            return new StringShape { Alignment = Alignment.Center, LineAlignment = Alignment.Center, Text = DefaultOffText, LineMode = LineMode.SingleLine };
        }

        /// <summary>
        /// Creates the default button shape.
        /// </summary>
        /// <returns>Shape.</returns>
        protected virtual Shape CreateDefaultButton()
        {
            return new ButtonShape { Color = Color.DimGray, Size = new Size(DefaultButtonWidth, 24) };
        }

        /// <summary>
        /// Creates the default shape if none is specified.
        /// </summary>
        /// <returns></returns>
        protected override Shape CreateDefaultBackground()
        {
            return new TextBoxShape();
        }

        protected override void OnLayout()
        {
            if (CheckFlag(InvalidationFlag.Color))
            {
                if (Background != null && !Color.IsEmpty) Background.Color = Color;
            }
            if (CheckFlag(InvalidationFlag.Layout | InvalidationFlag.Size | InvalidationFlag.Enabled))
            {
                Shapes.Clear();
                Shape onTextShape = On;
                Shape offTextShape = Off;

                if (Background != null) Shapes.AddFirst(Background);
                if (onBackgroundShape != null && Offset != 0) Shapes.AddLast(onBackgroundShape);
                if (onTextShape != null && Offset != 0) Shapes.AddLast(onTextShape);
                if (offTextShape != null && Offset != (1 << PercentShift)) Shapes.AddLast(offTextShape);
                if (contentChrome != null)
                {
                    Shapes.AddLast(contentChrome);
                    contentChrome.Bounds = Control.Scale(ClientBounds);
                }
                if (button != null) Shapes.AddLast(button);

                int left = ButtonLeft;
                int buttonWidth = Control.UnscaleX(button.Width);
                button.Bounds = Control.ScaledRect(left + buttonMargin.LeftZero, buttonMargin.TopZero, buttonWidth, Height - buttonMargin.Height);

                left += contentMargin.LeftZero;

                if (onBackgroundShape != null && Offset != 0)
                {
                    int right = ButtonOpacity == 255 ? button.X - contentMargin.RightZero : Width - contentMargin.Width;
                    Rectangle rect = new Rectangle(contentMargin.LeftZero, contentMargin.TopZero, right, Height - contentMargin.Height);
                    onBackgroundShape.Bounds = Control.Scale(rect);
                }

                int textWidth = Width - buttonWidth;
                if (ButtonOpacity != 255)
                {
                    int w = Width - contentMargin.Width;
                    buttonWidth = 0;
                    left = (IsChecked ? w : 0) + contentMargin.LeftZero;
                    textWidth = w;
                    //left = Width;
                }
                if (onTextShape != null) onTextShape.Bounds = Control.ScaledRect(left - textWidth, contentMargin.TopZero, textWidth, Height - contentMargin.Height);
                if (offTextShape != null) offTextShape.Bounds = Control.ScaledRect(left + buttonWidth, contentMargin.TopZero, textWidth, Height - contentMargin.Height);
            }
            base.OnLayout();
        }


        protected internal override void OnBeginPan(PanEventArgs e)
        {
            if (e.HorizontalMotion)
            {
                e.Handled = true;
                e.Value = new Point((offset * Width) >> PercentShift, 0);
            }
            base.OnBeginPan(e);
        }

        internal protected override void OnPan(PanEventArgs e)
        {
            e.Handled = true;
            Offset = (e.Value.X << PercentShift) / Width;

            base.OnPan(e);
        }

        /// <summary>
        /// Sets all properties to a defined value.
        /// If overridden, this method determines the ideal values of specific properties, for instance, when a property is changed as the
        /// uses swipes between possible values.
        /// It's best practice to access each property value  using PropertyAccessor.GetUnanimatedValue.
        /// </summary>
        /// <param name="animated">True when to allowed to tranistion to a new value, otherwise false.</param>
        public override void EnsureRestState(bool animated)
        {
            int d0 = Offset;
            int d1 = MaxOffset - Offset;
            bool isChecked = d0 > d1;
            if (isChecked != IsChecked)
            {
                IsChecked = isChecked;
            }
            else
            {
                if (d0 != 0f && d0 != MaxOffset)
                {
                    d0 = isChecked ? MaxOffset : 0;
                    IntTransition transition = new IntTransition { Property = OffsetProperty, Duration = 300, To = d0 };
                    OffsetProperty.BeginAnimation(this, transition);
                }
            }
            base.EnsureRestState(animated);
        }

        protected override void EnsureRestStateOnSizeChanged()
        {
        }

        protected internal override void OnFlick(TouchEventArgs e)
        {
            if (!e.Handled)
            {
                switch (e.Direction)
                {
                    case PanDirection.Left:
                        e.Handled = true;
                        IsChecked = false;
                        break;

                    case PanDirection.Right:
                        e.Handled = true;
                        IsChecked = true;
                        break;
                }
            }
            base.OnFlick(e);
        }

        private void SetOffsetAnimated(int offset)
        {
            IntTransition transition = new IntTransition { Property = OffsetProperty, Duration = 300, To = offset };
            OffsetProperty.BeginAnimation(this, transition);
        }


        #endregion
    }
}

